import copy

from PyQt4 import QtGui, QtCore


def getItemCoordinates(item):
    """
    Get the item coordinates from the alignment perspective
    The coordinate (i, j) means:
        the jth item of the ith sequence
    """
    itemPos = item.scenePos()
    
    row = (itemPos.y() - item.view.frameMarginTop) / \
                    (2 * item.view.ballRadius + item.view.ballSpacing)
    col = (itemPos.x() - item.view.frameMarginLeft) / \
                    (2 * item.view.ballRadius)
    
    return (int(row), int(col))


def getSelectedLinesCoordinates(selectedItems):
    """Get the first item of each selected line"""
    selectedLines = {}
    
    for eachItem in selectedItems:
        if eachItem in eachItem.view.alignmentLines:
            return {}
        
        row, col = getItemCoordinates(eachItem)
        
        if row not in selectedLines.keys():
            selectedLines[row] = [col]
        else:
            selectedLines[row].append(col)
    
    for row in selectedLines.keys():
        selectedLines[row].sort()
    
    selectedLinesIntervals = {}
    for row in selectedLines.keys():
        selectedCols = selectedLines[row]
        selectedLinesIntervals[row] = [[selectedCols[0], selectedCols[0]]]
        
        for col in selectedCols[1:]:
            if col - 1 > selectedLinesIntervals[row][-1][1]:
                selectedLinesIntervals[row].append([col, col])
            else:
                selectedLinesIntervals[row][-1][1] = col
        
        if len(selectedLinesIntervals[row]) > 1:
#            messageBox = QtGui.QMessageBox()
#            messageBox.setWindowTitle('Error')
#            messageBox.setText('You selected multiple intervals in some line(s). '
#                                        'This is not allowed. Go back and change the selection.')
#            messageBox.addButton(QtGui.QMessageBox.Ok)
#    
#            messageBox.exec_()
            return {}
    
    selectedLines = {row : selectedLinesIntervals[row][0] 
                     for row in selectedLinesIntervals.keys()}
    return selectedLines


def getValidOffsetCol(item, previousX, currentX, selectedLines, sequences):
    """
    Compute the valid offset to move the selected items
    Items must be bounded by the left line
    Also, items must be bounded by adjacent non-gap items
    """
    offsetX = currentX - previousX
    offsetCol = offsetX / ( 2 * item.view.ballRadius)
    offsetCol = int(round(offsetCol, 0))
    
    if offsetCol == 0:
        return offsetCol
    
    if offsetCol > 0:
        for i in range(1, offsetCol+1):
            for row in selectedLines.keys():
                col = selectedLines[row][1]
                if col+1 != len(sequences[row]) and sequences[row][col+i] != '-':
                    return i-1
        
        return offsetCol
    
    for i in range(-1, offsetCol-1, -1):        
        for row in selectedLines.keys():
            col = selectedLines[row][0]
            if col+i < 0 or sequences[row][col+i] != '-':
                return i+1
    
    return offsetCol


def updateWithGaps(item, sequences, selectedLines, offsetCol):
    """Update the alignment image as well as the alignment sequences"""
    alignUnitItems = item.view.alignUnitItems
    if offsetCol == 0:
        return
    
    for row in selectedLines.keys():
        colStart = selectedLines[row][0]
        colEnd = selectedLines[row][1]
        rowLen = len(sequences[row])
        seqOld = sequences[row]
        lineItems = alignUnitItems[row]
        newLineItems = []
        
        if offsetCol > 0:
            gaps = '-' * offsetCol
            seqNew = seqOld[0:colStart] + gaps
            
            newLineItems.extend(lineItems[0:colStart])
            pos0 = lineItems[colStart].scenePos()
            
            for i in range(offsetCol):
                unitItem = RUnitGapItem(item.window, item.view.ballRadius)
                unitItem.setPen(QtGui.QPen(QtCore.Qt.black, item.view.ballRadius / 3))
                unitItem.setPos( pos0.x()+ i * 2 * item.view.ballRadius, pos0.y())
                unitItem.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
                unitItem.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
                
                newLineItems.append(unitItem)
            
            if colEnd+1 == rowLen:
                seqNew += seqOld[colStart:]
                newLineItems.extend(lineItems[colStart:])
            else:
                seqNew += seqOld[colStart:colEnd+1]
                seqNew += seqOld[colEnd+offsetCol+1:]
                newLineItems.extend(lineItems[colStart:colEnd+1])
                newLineItems.extend(lineItems[colEnd+offsetCol+1:])
                
                for col in range(colEnd+1, colEnd+offsetCol+1):
                    item.scene().removeItem(lineItems[col])
            
        else:
            seqNew = seqOld[0:colStart+offsetCol] + seqOld[colStart:colEnd+1]
            newLineItems.extend(lineItems[0:colStart+offsetCol])
            newLineItems.extend(lineItems[colStart:colEnd+1])
            
            for col in range(colStart+offsetCol, colStart):
                item.scene().removeItem(lineItems[col])
            
            if colEnd+1 == rowLen:
                seqNew += seqOld[colEnd+1:]
                newLineItems.extend(lineItems[colEnd+1:])
            else:
                gaps = '-' * abs(offsetCol)
                seqNew += gaps + seqOld[colEnd+1:]
                pos0 = lineItems[colEnd].scenePos()

                for i in range(abs(offsetCol)):
                    unitItem = RUnitGapItem(item.window, item.view.ballRadius)
                    unitItem.setPen(QtGui.QPen(QtCore.Qt.black, item.view.ballRadius / 3))
                    unitItem.setPos( pos0.x()+ i * 2 * item.view.ballRadius, pos0.y())
                    unitItem.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
                    unitItem.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
                    
                    newLineItems.append(unitItem)
                
                newLineItems.extend(lineItems[colEnd+1:])
        
        sequences[row] = seqNew
        alignUnitItems[row] = newLineItems
    
    item.view.sequences = sequences
    item.view.alignUnitItems = alignUnitItems
    
    maxLen = max([len(seq) for seq in sequences])
    item.view.maxCol = maxLen
    
    item.window.seqData['seq'] = sequences
    item.window.seqData['maxSeqLen'] = maxLen
    
    item.window.currentViewSaved = False
    item.window.currentAlignmentSaved = False


def determineActionsToEnable(item):
    
    if not item:
        return
        
    selectedItems = item.scene().selectedItems()
    if not selectedItems:
        item.window.moveRightAction.setEnabled(False)
        item.window.moveLeftAction.setEnabled(False)
        item.window.moveUpAction.setEnabled(False)
        item.window.moveDownAction.setEnabled(False)
        item.window.deleteTruncateAction.setEnabled(False)
        item.window.removeAlignLineAction.setEnabled(False)
        item.window.addRegionShadeAction.setEnabled(False)
        item.window.removeRegionShadeAction.setEnabled(False)
        return
    
    for selectedItem in selectedItems:
        if selectedItem not in item.view.alignmentLines:
            item.window.removeAlignLineAction.setEnabled(False)
            break
    else:
        item.window.removeAlignLineAction.setEnabled(True)
        item.window.moveRightAction.setEnabled(False)
        item.window.moveLeftAction.setEnabled(False)
        item.window.moveUpAction.setEnabled(False)
        item.window.moveDownAction.setEnabled(False)
        item.window.deleteTruncateAction.setEnabled(False)
        item.window.addRegionShadeAction.setEnabled(False)
        item.window.removeRegionShadeAction.setEnabled(False)
        return
    
    selectedLines = getSelectedLinesCoordinates(selectedItems)
    if not selectedLines:
        item.window.moveRightAction.setEnabled(False)
        item.window.moveLeftAction.setEnabled(False)
        item.window.moveUpAction.setEnabled(False)
        item.window.moveDownAction.setEnabled(False)
        item.window.deleteTruncateAction.setEnabled(False)
        item.window.addRegionShadeAction.setEnabled(False)
        item.window.removeRegionShadeAction.setEnabled(False)
        return

    if len(selectedLines) != 1:
        item.window.addRegionShadeAction.setEnabled(False)
        item.window.removeRegionShadeAction.setEnabled(False)
    else:
        selectedRow = list(selectedLines.keys())[0]
        selectedRegion = tuple(selectedLines[selectedRow])
        
        for shadowedRegion in item.view.shadowedRegions.keys():
            if not (selectedRegion[1] < shadowedRegion[0] or \
                        selectedRegion[0] > shadowedRegion[1]):
                item.window.addRegionShadeAction.setEnabled(False)
                break
        else:                
            item.window.addRegionShadeAction.setEnabled(True)
        
        for shadowedRegion in item.view.shadowedRegions.keys():
            if selectedRegion == shadowedRegion:
                item.window.removeRegionShadeAction.setEnabled(True)
                break
        else:
            item.window.removeRegionShadeAction.setEnabled(False)
    
    for row in selectedLines:
        upper = selectedLines[row][1]
        if upper+1 != len(item.view.sequences[row]) and \
                        item.view.sequences[row][upper+1] != '-':
            item.window.moveRightAction.setEnabled(False)
            break
    else:
        item.window.moveRightAction.setEnabled(True)
    
    for row in selectedLines:
        lower = selectedLines[row][0]
        if lower == 0 or item.view.sequences[row][lower-1] != '-':
            item.window.moveLeftAction.setEnabled(False)
            break
    else:
        item.window.moveLeftAction.setEnabled(True)
    
    rows = list(selectedLines.keys())
    rows.sort()
    
#    deleteTruncateActionSet = False
    for row in rows:
        rowLen = len(item.view.sequences[row])
        if not item.view.alignUnitItems[row][0].isSelected() and \
                    not item.view.alignUnitItems[row][-1].isSelected():
            item.window.deleteTruncateAction.setEnabled(False)
            break
#        for col in range(rowLen):
#            if not item.view.alignUnitItems[row][col].isSelected():
#                item.window.deleteTruncateAction.setEnabled(False)
#                deleteTruncateActionSet = True
#                break
#        
#        if deleteTruncateActionSet:
#            break
    else:
        item.window.deleteTruncateAction.setEnabled(True)
    
    for row in range(rows[0], rows[-1]+1):
        rowLen = len(item.view.sequences[row])
        for col in range(rowLen):
            if not item.view.alignUnitItems[row][col].isSelected():
                item.window.moveUpAction.setEnabled(False)
                item.window.moveDownAction.setEnabled(False)
                return
    
    if item.view.alignUnitItems[0][0].isSelected():
        item.window.moveUpAction.setEnabled(False)
    else:
        item.window.moveUpAction.setEnabled(True)
    
    if item.view.alignUnitItems[len(item.view.sequences)-1][0].isSelected():
        item.window.moveDownAction.setEnabled(False)
    else:
        item.window.moveDownAction.setEnabled(True)


def mousePressEventImpl(item, event):
    """This function reimplements the mouse press event handler"""
    alignUnitItems = item.view.alignUnitItems
    row, col = getItemCoordinates(item)
    
    selectedItems = item.scene().selectedItems()
    modifier = event.modifiers()
    
    if modifier == QtCore.Qt.ControlModifier:
        for eachItem in selectedItems:
            if eachItem in item.view.alignmentLines:
                return
        
        if item in selectedItems:
            for eachItem in alignUnitItems[row][col:]:
                eachItem.setSelected(False)
        else:
            for eachItem in alignUnitItems[row][col:]:
                eachItem.setSelected(True)
        
    elif modifier == QtCore.Qt.ShiftModifier:
        for eachItem in selectedItems:
            if eachItem in item.view.alignmentLines:
                return
        
        if item.view.lastSelectedLineHead:
            lastRow, lastCol = item.view.lastSelectedLineHead
            if lastCol == col:
                if lastRow < row:
                    for iRow in range(lastRow+1, row+1):
                        for eachItem in alignUnitItems[iRow][col:]:
                            eachItem.setSelected(True)
                elif lastRow > row:
                    for iRow in range(row, lastRow):
                        for eachItem in alignUnitItems[iRow][col:]:
                            eachItem.setSelected(True)
        else:
            for eachItem in alignUnitItems[row][col:]:
                eachItem.setSelected(True)
        
    elif selectedItems and (item not in selectedItems):      
        for eachItem in selectedItems:            
            eachItem.setSelected(False)
        
        for eachItem in alignUnitItems[row][col:]:        
            eachItem.setSelected(True)
        
    elif not selectedItems:
        for eachItem in alignUnitItems[row][col:]:        
            eachItem.setSelected(True)        


def mouseMoveEventImpl(item, event):
    """This function reimplements the mouse move event handler"""
    selectedItems = item.scene().selectedItems()    
    if selectedItems is None:
        return

    for eachItem in selectedItems:
        if eachItem in item.view.alignmentLines:
            return
    
    buttonDownPos = event.buttonDownScenePos(QtCore.Qt.LeftButton)
    mouseCurrentPos = event.scenePos()
    
    if item.contains(item.mapFromScene(buttonDownPos)):
        previousX = buttonDownPos.x()
    else:
        previousX = item.scenePos().x()
    
    selectedLinesCoordinates = getSelectedLinesCoordinates(selectedItems)
    sequences = item.view.sequences
    
    if not selectedLinesCoordinates:
        return
    
    validOffsetCol = getValidOffsetCol(item, previousX, mouseCurrentPos.x(), 
                                       selectedLinesCoordinates, sequences)
    
    if validOffsetCol == 0:
        return

    updateWithGaps(item, sequences, selectedLinesCoordinates, validOffsetCol)
    item.window.itemMoved = True
    
    for eachItem in selectedItems:        
        previousPos = eachItem.scenePos()
        newPos = QtCore.QPointF(previousPos.x() + \
                    validOffsetCol * 2 * item.view.ballRadius, previousPos.y())
        eachItem.setPos(newPos)


def mouseReleaseEventImpl(item, event):
    """This function reimplements the mouse release event handler"""
#    modifier = event.modifiers()    
#    if modifier != QtCore.Qt.ControlModifier:        
#        for eachItem in item.scene().selectedItems():            
#            eachItem.setSelected(False)

    alignUnitItems = item.view.alignUnitItems
    row, col = getItemCoordinates(item)
    if item.isSelected():
        head = 0
        while not alignUnitItems[row][head].isSelected():
            head += 1
        item.view.lastSelectedLineHead = row, head

    if item.window.itemMoved:
        item.view.lastSelectedLineHead = None
    
    item.scene().removeItem(item.view.frameLineTop)
    item.scene().removeItem(item.view.frameLineLeft)
    for eachItem in item.view.colNumItems:
        item.scene().removeItem(eachItem)
    
    item.view.drawFrame(item.window)
    item.view.drawColNum(item.window)
    
    if not item.window.lineNumViewAction.isChecked():
        item.view.frameLineLeft.setVisible(False)
    
    if not item.window.colNumViewAction.isChecked():
        item.view.frameLineTop.setVisible(False)
        
        for eachItem in item.view.colNumItems:
            eachItem.setVisible(False)
    
    for alignmentLine in item.view.alignmentLines:
        if alignmentLine.scenePos().x() > item.view.frameMarginLeft + \
                                                    item.view.frameWidth: 
            alignmentLine.setPos(item.view.frameMarginLeft + item.view.frameWidth, \
                                           item.view.alignmentLinePosY)
    
    newRegion = None
    shadowedRegionsCopy = copy.deepcopy(list(item.view.shadowedRegions.keys()))
    for shadowedRegion in shadowedRegionsCopy:
        if shadowedRegion[0] >= item.window.seqData['maxSeqLen']:
            item.scene().removeItem(item.view.shadowedRegions[shadowedRegion][1])
            item.view.shadowedRegions.pop(shadowedRegion, None)
        elif shadowedRegion[1] >= item.window.seqData['maxSeqLen'] - 1:
            newRegion = (shadowedRegion[0], item.window.seqData['maxSeqLen'] - 1)
            newColor = item.view.shadowedRegions[shadowedRegion][0]
            item.scene().removeItem(item.view.shadowedRegions[shadowedRegion][1])
            item.view.shadowedRegions.pop(shadowedRegion, None)

    if newRegion:
        item.view.drawRegionShadow(item.window, newRegion, newColor)


def moveRightActionBody(window):
    
        selectedItems = window.scene.selectedItems()
        selectedLinesCoordinates = getSelectedLinesCoordinates(selectedItems)

        if not selectedLinesCoordinates:
            return
        
        updateWithGaps(selectedItems[0], window.seqData['seq'], 
                       selectedLinesCoordinates, 1)
        
        window.scene.clearSelection()
        window.refreshViewAction.trigger()
        for row in selectedLinesCoordinates:
            lower, upper = selectedLinesCoordinates[row]
            for col in range(lower, upper+1):
                window.view.alignUnitItems[row][col+1].setSelected(True)
        
        determineActionsToEnable(window.scene.selectedItems()[0])


def moveLeftActionBody(window):
    
        selectedItems = window.scene.selectedItems()
        selectedLinesCoordinates = getSelectedLinesCoordinates(selectedItems)

        if not selectedLinesCoordinates:
            return
        
        updateWithGaps(selectedItems[0], window.seqData['seq'], 
                       selectedLinesCoordinates, -1)
        
        window.scene.clearSelection()
        window.refreshViewAction.trigger()
        for row in selectedLinesCoordinates:
            lower, upper = selectedLinesCoordinates[row]
            for col in range(lower, upper+1):
                window.view.alignUnitItems[row][col-1].setSelected(True)
        
        determineActionsToEnable(window.scene.selectedItems()[0])


def moveUpActionBody(window):
    
        selectedItems = window.scene.selectedItems()
        selectedLinesCoordinates = getSelectedLinesCoordinates(selectedItems)
        rows = list(selectedLinesCoordinates.keys())
        rows.sort()
        
        firstRow = rows[0]
        lastRow = rows[-1]
        seqAbove = window.seqData['seq'][firstRow-1]
        idAbove = window.seqData['id'][firstRow-1]
        seqAboveNoNewGaps = window.seqDataNoNewGaps['seq'][firstRow-1]
        
        for row in range(firstRow-1, lastRow):
            window.seqData['seq'][row] = window.seqData['seq'][row+1]
            window.seqData['id'][row] = window.seqData['id'][row+1]
            window.seqDataNoNewGaps['seq'][row] = window.seqDataNoNewGaps['seq'][row+1]
            window.seqDataNoNewGaps['id'][row] = window.seqDataNoNewGaps['id'][row+1]
        
        window.seqData['seq'][lastRow] = seqAbove
        window.seqData['id'][lastRow] = idAbove
        window.seqDataNoNewGaps['seq'][lastRow] = seqAboveNoNewGaps
        window.seqData['id'][lastRow] = idAbove
        
        window.scene.clearSelection()
        window.refreshViewAction.trigger()
        for row in range(firstRow-1, lastRow):
            for col in range(len(window.seqData['seq'][row])):
                window.view.alignUnitItems[row][col].setSelected(True)
        
        determineActionsToEnable(window.view.alignUnitItems[firstRow-1][0])


def moveDownActionBody(window):
    
        selectedItems = window.scene.selectedItems()
        selectedLinesCoordinates = getSelectedLinesCoordinates(selectedItems)
        rows = list(selectedLinesCoordinates.keys())
        rows.sort()
        
        firstRow = rows[0]
        lastRow = rows[-1]
        seqBelow = window.seqData['seq'][lastRow+1]
        idBelow = window.seqData['id'][lastRow+1]
        seqBelowNoNewGaps = window.seqDataNoNewGaps['seq'][lastRow+1]
        
        for row in range(lastRow+1, firstRow, -1):
            window.seqData['seq'][row] = window.seqData['seq'][row-1]
            window.seqData['id'][row] = window.seqData['id'][row-1]
            window.seqDataNoNewGaps['seq'][row] = window.seqDataNoNewGaps['seq'][row-1]
            window.seqDataNoNewGaps['id'][row] = window.seqDataNoNewGaps['id'][row-1]
        
        window.seqData['seq'][firstRow] = seqBelow
        window.seqData['id'][firstRow] = idBelow
        window.seqDataNoNewGaps['seq'][firstRow] = seqBelowNoNewGaps
        window.seqData['id'][firstRow] = idBelow
        
        window.scene.clearSelection()
        window.refreshViewAction.trigger()
        for row in range(firstRow+1, lastRow+2):
            for col in range(len(window.seqData['seq'][row])):
                window.view.alignUnitItems[row][col].setSelected(True)
        
        determineActionsToEnable(window.view.alignUnitItems[firstRow+1][0])


class MouseMoveCommand(QtGui.QUndoCommand):
    def __init__(self, view, window):
        QtGui.QUndoCommand.__init__(self)
        self.oldSeqData = copy.deepcopy(window.oldSeqData)
        self.newSeqData = copy.deepcopy(window.newSeqData)
        self.oldSelected = copy.deepcopy(window.oldSelected)
        self.newSelected = copy.deepcopy(window.newSelected)
        
        self.view = view
        self.window = window
    
    def undo(self):
        self.window.seqData = copy.deepcopy(self.oldSeqData)
        self.window.scene.clearSelection()
        self.window.refreshViewAction.trigger()
        for coordinate in self.oldSelected:
            self.view.alignUnitItems[coordinate[0]][coordinate[1]].setSelected(True)
    
    def redo(self):     
        self.window.seqData = copy.deepcopy(self.newSeqData)
        self.window.scene.clearSelection()
        self.window.refreshViewAction.trigger()
        for coordinate in self.newSelected:
            self.view.alignUnitItems[coordinate[0]][coordinate[1]].setSelected(True)


class ActionMoveCommand(QtGui.QUndoCommand):
    def __init__(self, action, window):
        QtGui.QUndoCommand.__init__(self)
        self.action = action
        self.window = window
        self.movedItems = []
        for item in window.scene.selectedItems():
            self.movedItems.append(getItemCoordinates(item))
    
    def undo(self):        
        if self.action == 'RIGHT':
            moveLeftActionBody(self.window)
        elif self.action == 'LEFT':
            moveRightActionBody(self.window)
        elif self.action == 'UP':
            moveDownActionBody(self.window)
        elif self.action == 'DOWN':
            moveUpActionBody(self.window)
    
    def redo(self):        
        alignUnitItems = self.window.view.alignUnitItems
        self.window.scene.clearSelection()
        for coordinate in self.movedItems:
            alignUnitItems[coordinate[0]][coordinate[1]].setSelected(True)
        
        if self.action == 'RIGHT' and self.window.moveRightAction.isEnabled():
            moveRightActionBody(self.window)
        elif self.action == 'LEFT' and self.window.moveLeftAction.isEnabled():
            moveLeftActionBody(self.window)
        elif self.action == 'UP' and self.window.moveUpAction.isEnabled():
            moveUpActionBody(self.window)
        elif self.action == 'DOWN' and self.window.moveDownAction.isEnabled():
            moveDownActionBody(self.window)


class DeleteTruncateCommand(QtGui.QUndoCommand):
    def __init__(self, window):
        QtGui.QUndoCommand.__init__(self)
        self.window = window
        self.oldSeqData = copy.deepcopy(window.seqData)
#        self.newSeqData = None
        self.oldSeqDataNoNewGaps = copy.deepcopy(window.seqDataNoNewGaps)
#        self.newSeqDataNoNewGaps = None
        self.oldSelectedItems = window.scene.selectedItems()
#        self.newSelectedItems = None
        self.oldSelectedLines = getSelectedLinesCoordinates(self.oldSelectedItems)
#        self.newSelectedLines = None
    
    def undo(self):
        self.window.seqData = copy.deepcopy(self.oldSeqData)
        self.window.seqDataNoNewGaps = copy.deepcopy(self.oldSeqDataNoNewGaps)
        self.window.scene.clearSelection()
        self.window.refreshViewAction.trigger()
#        self.window.scene.clear()
#        self.window.view.drawView(self.window)
        for row in self.oldSelectedLines.keys():
            selectionInterval = self.oldSelectedLines[row]
            for col in range(selectionInterval[0], selectionInterval[1]+1):
                self.window.view.alignUnitItems[row][col].setSelected(True)
        determineActionsToEnable(self.window.view.alignUnitItems[0][0])
    
    def redo(self):
        rowsAll = self.oldSelectedLines.keys()
        rowsToDelete = []
        rowsToTruncate = []
        
        for row in rowsAll:
            rowLen = len(self.oldSeqData['seq'][row])
            if self.oldSelectedLines[row][0] == 0 and self.oldSelectedLines[row][1] == rowLen - 1:
                rowsToDelete.append(row)
            elif self.oldSelectedLines[row][0] == 0 or self.oldSelectedLines[row][1] == rowLen - 1:
                rowsToTruncate.append(row)
        
        # do truncation first
        for row in rowsToTruncate:
            truncateStart = self.oldSelectedLines[row][0]
            truncateEnd = self.oldSelectedLines[row][1]
            rowToTruncate = self.window.seqData['seq'][row]
            rowToTruncateNoNewGaps = self.window.seqDataNoNewGaps['seq'][row]
            
            if truncateStart == 0:
                colNoNewGaps = 0
                for col in range(truncateEnd + 1):
                    if rowToTruncate[col] == rowToTruncateNoNewGaps[colNoNewGaps]:
                        colNoNewGaps += 1
                    elif rowToTruncateNoNewGaps[colNoNewGaps] == '-':
                        while rowToTruncateNoNewGaps[colNoNewGaps] != rowToTruncate[col]:
                            colNoNewGaps += 1
                        colNoNewGaps += 1
                
                self.window.seqDataNoNewGaps['seq'][row] = \
                                            self.window.seqDataNoNewGaps['seq'][row][colNoNewGaps:]
            else:
                colNoNewGaps = len(rowToTruncateNoNewGaps) - 1
                for col in range(truncateEnd, truncateStart -1, -1):
                    if rowToTruncate[col] == rowToTruncateNoNewGaps[colNoNewGaps]:
                        colNoNewGaps -= 1
                    elif rowToTruncateNoNewGaps[colNoNewGaps] == '-':
                        while rowToTruncateNoNewGaps[colNoNewGaps] != rowToTruncate[col]:
                            colNoNewGaps -= 1
                        colNoNewGaps -= 1
                
                self.window.seqDataNoNewGaps['seq'][row] = \
                                            self.window.seqDataNoNewGaps['seq'][row][:colNoNewGaps+1]

        self.window.seqDataNoNewGaps['maxSeqLen'] = max([len(seq) 
                                                  for seq in self.window.seqDataNoNewGaps['seq']])
        
        for row in rowsToTruncate:
            if self.oldSelectedLines[row][0] == 0:
                self.window.seqData['seq'][row] = \
                                self.window.seqData['seq'][row][self.oldSelectedLines[row][1]+1:]
            else:
                self.window.seqData['seq'][row] = \
                                self.window.seqData['seq'][row][:self.oldSelectedLines[row][0]]
        
        self.window.seqData['maxSeqLen'] = max([len(seq) for seq in self.window.seqData['seq']])
        
        # do deletion now
        self.window.seqData['seq'] = [self.window.seqData['seq'][i] 
                               for i in range(len(self.window.seqData['seq'])) 
                               if i not in rowsToDelete]
        
        self.window.seqData['id'] = [self.window.seqData['id'][i] 
                              for i in range(len(self.window.seqData['id'])) 
                              if i not in rowsToDelete]
        
        self.window.seqData['num'] = len(self.window.seqData['seq'])
        if self.window.seqData['num'] == 0:
            self.window.seqData['maxSeqLen'] = 0
        else:
            self.window.seqData['maxSeqLen'] = max([len(seq) 
                                                    for seq in self.window.seqData['seq']])
        
        self.window.seqDataNoNewGaps['seq'] = [self.window.seqDataNoNewGaps['seq'][i] 
                               for i in range(len(self.window.seqDataNoNewGaps['seq'])) 
                               if i not in rowsToDelete]
        
        self.window.seqDataNoNewGaps['id'] = [self.window.seqDataNoNewGaps['id'][i] 
                              for i in range(len(self.window.seqDataNoNewGaps['id'])) 
                              if i not in rowsToDelete]
        
        self.window.seqDataNoNewGaps['num'] = len(self.window.seqDataNoNewGaps['seq'])
        if self.window.seqDataNoNewGaps['num'] == 0:
            self.window.seqDataNoNewGaps['maxSeqLen'] = 0
        else:
            self.window.seqDataNoNewGaps['maxSeqLen'] = max([len(seq) 
                                                  for seq in self.window.seqDataNoNewGaps['seq']])
        
        self.window.scene.clearSelection()
        self.window.refreshViewAction.trigger()
#        self.window.scene.clear()
#        self.window.view.drawView(self.window)
        notSelectMore = False
        for row in self.oldSelectedLines.keys():
            if row >= len(self.window.seqData['seq']):
                notSelectMore = True
                break
        
        if not notSelectMore:
            for row in rowsToDelete:
                rowLen = len(self.window.seqData['seq'][row])
                for col in range(rowLen):
                    self.window.view.alignUnitItems[row][col].setSelected(True)

        if self.window.seqData['num'] == 0:
            self.window.selectAllAction.setEnabled(False)
            self.window.deleteTruncateAction.setEnabled(False)
            self.window.removeEditedGapsAction.setEnabled(False)
            self.window.moveRightAction.setEnabled(False)
            self.window.moveLeftAction.setEnabled(False)
            self.window.moveUpAction.setEnabled(False)
            self.window.moveRightAction.setEnabled(False)
        else:
            determineActionsToEnable(self.window.view.alignUnitItems[0][0])


class RUnitBallItem(QtGui.QGraphicsEllipseItem):
    """Item class for amino acids in alignment"""
    def __init__(self, window, radius):        
        QtGui.QGraphicsEllipseItem.__init__(self, 0, 0, 2*radius, 2*radius, \
                                            None, window.scene)
        
        self.window = window
        self.view = window.view
        self.setCursor(QtCore.Qt.PointingHandCursor)
    
    def mousePressEvent(self, event):
        
        mousePressEventImpl(self, event)
        self.window.itemMoved = False
        self.window.oldSeqData = copy.deepcopy(self.window.seqData)
        self.window.oldSelected = []
        for item in self.scene().selectedItems():
            self.window.oldSelected.append(getItemCoordinates(item))
    
    def mouseMoveEvent(self, event):
        
        mouseMoveEventImpl(self, event)
    
    def mouseReleaseEvent(self, event):
        
        mouseReleaseEventImpl(self, event)
        determineActionsToEnable(self)
        self.window.newSeqData = copy.deepcopy(self.window.seqData)
        self.window.newSelected = []
        for item in self.scene().selectedItems():
            if item not in self.view.alignmentLines:
                self.window.newSelected.append(getItemCoordinates(item))
        
        if self.window.itemMoved:
            self.window.undoAction.setEnabled(True)
            self.window.redoAction.setEnabled(True)
            mouseMoveCmd = MouseMoveCommand(self.view, self.window)
            self.window.undoStack.push(mouseMoveCmd)


class RUnitGapItem(QtGui.QGraphicsLineItem):
    """Item class for gaps in alignment"""
    def __init__(self, window, radius):
        QtGui.QGraphicsLineItem.__init__(self, 0.3*radius, radius, \
                                    1.7*radius, radius, None, window.scene)
        
        self.window = window
        self.view = window.view
        self.setCursor(QtCore.Qt.PointingHandCursor)
    
    def mousePressEvent(self, event):
        
        mousePressEventImpl(self, event)
        self.window.itemMoved = False
        self.window.oldSeqData = copy.deepcopy(self.window.seqData)
        self.window.oldSelected = []
        for item in self.scene().selectedItems():
            self.window.oldSelected.append(getItemCoordinates(item))
    
    def mouseMoveEvent(self, event):
        
        mouseMoveEventImpl(self, event)

    def mouseReleaseEvent(self, event):
        
        mouseReleaseEventImpl(self, event)
        determineActionsToEnable(self)
        self.window.newSeqData = copy.deepcopy(self.window.seqData)
        self.window.newSelected = []
        for item in self.scene().selectedItems():
            if item not in self.view.alignmentLines:
                self.window.newSelected.append(getItemCoordinates(item))
       
        if self.window.itemMoved:
            self.window.undoAction.setEnabled(True)
            self.window.redoAction.setEnabled(True)
            mouseMoveCmd = MouseMoveCommand(self.view, self.window)
            self.window.undoStack.push(mouseMoveCmd)


class RColNumTextItem(QtGui.QGraphicsTextItem):
    """Item class for column numbers"""
    def __init__(self, window, text):
        QtGui.QGraphicsTextItem.__init__(self, text)
        
        self.window = window
        self.view = window.view
        self.setCursor(QtCore.Qt.PointingHandCursor)
    
    def mousePressEvent(self, event):
        
        itemCenterPos = self.boundingRect().center()
        
        self.view.colIndicatorLine = QtGui.QGraphicsLineItem(0, 0, 0, 
                                self.view.frameHeight, None, self.window.scene)
        self.view.colIndicatorLine.setPos(
                                self.scenePos().x() + itemCenterPos.x(), 
                                self.view.frameMarginTop)
        
        pen = QtGui.QPen(self.view.pen)
        pen.setStyle(QtCore.Qt.DashLine)
        self.view.colIndicatorLine.setPen(pen)
    
    def mouseReleaseEvent(self, event):
        
        if self.view.colIndicatorLine:
            self.window.scene.removeItem(self.view.colIndicatorLine)
            self.view.colIndicatorLine = None


class RSeqNumTextItem(QtGui.QGraphicsTextItem):
    """Item class for line numbers (the same as sequence numbers)"""
    def __init__(self, window, text):
        QtGui.QGraphicsTextItem.__init__(self, text)
        
        self.window = window
        self.view = window.view
        self.setCursor(QtCore.Qt.PointingHandCursor)
    
    def hoverEnterEvent(self, event):
        
        itemCenterPos = self.boundingRect().center()
        itemPos = self.scenePos()
        offsetY = itemPos.y() + itemCenterPos.y() - self.view.frameMarginTop
        row = int( (offsetY - self.view.ballRadius) / \
                        (2 * self.view.ballRadius + self.view.ballSpacing) )
        
        if self.window.seqData and self.window.seqData['id']:
            text = self.window.seqData['id'][row]
        else:
            text = ''
        
        self.view.seqIndicatorText = QtGui.QGraphicsTextItem(text)
        self.view.seqIndicatorText.setFont(self.font())
        self.view.seqIndicatorText.setPos(self.view.frameMarginLeft, 
                        itemPos.y() + itemCenterPos.y() - 
                        self.view.seqIndicatorText.boundingRect().height() / 2)
        
        self.window.scene.addItem(self.view.seqIndicatorText)
    
    def hoverLeaveEvent(self, event):
        
        self.window.scene.removeItem(self.view.seqIndicatorText)


class RGraphicsLineItem(QtGui.QGraphicsLineItem):
    """Item class for alignment line"""
    def __init__(self, window, x1, y1, x2, y2):
        QtGui.QGraphicsLineItem.__init__(self, x1, y1, x2, y2, 
                                         None, window.scene)
        
        self.window = window
        self.view = window.view
        self.scaleX = 1
        self.scaleY = 1
        self.setCursor(QtCore.Qt.PointingHandCursor)
    
    def hoverEnterEvent(self, event):
        
        self.scale(3, 1)
        self.scaleX *= 3
    
    def hoverLeaveEvent(self, event):
        
        self.scale(1/self.scaleX, 1)
        self.scaleX = 1
    
    def mousePressEvent(self, event):
        selectedItems = self.scene().selectedItems()
        modifier = event.modifiers()
        
        if modifier == QtCore.Qt.ControlModifier:
            for eachItem in selectedItems:
                if eachItem not in self.view.alignmentLines:
                    return
            
            if self in selectedItems:
                self.setSelected(False)
            else:
                self.setSelected(True)
            
        elif selectedItems and (self not in selectedItems):      
            for eachItem in selectedItems:            
                eachItem.setSelected(False)
            
            self.setSelected(True)
            
        elif not selectedItems:
            self.setSelected(True)
    
    def mouseMoveEvent(self, event):
        
        selectedItems = self.scene().selectedItems()
        for item in selectedItems:
            if item not in self.view.alignmentLines:
                return
        
        if len(selectedItems) > 1:
            messageBox = QtGui.QMessageBox()
            messageBox.setWindowTitle('Error')
            messageBox.setText('You selected multiple alignment lines. '
                                    'Moving multiple lines at the same time is not allowed.')
            messageBox.addButton(QtGui.QMessageBox.Ok)
            messageBox.exec_()
            return
        
        mouseCurrentPos = event.scenePos()
        validX = self.getValidX(mouseCurrentPos.x())
        self.setPos(validX, self.view.alignmentLinePosY)
    
    def mouseReleaseEvent(self, event):
        determineActionsToEnable(self)
    
    def getValidX(self, newX):
        
        if newX < self.view.frameMarginLeft:
            newX = self.view.frameMarginLeft
            
        elif newX > self.view.frameMarginLeft + self.view.frameWidth:
            newX = self.view.frameMarginLeft + self.view.frameWidth
        
        newXIndex = int((newX - self.view.frameMarginLeft) / \
                        (self.view.ballRadius * 2) + 0.5)
        newX = newXIndex * 2 * self.view.ballRadius
        
        return newX + self.view.frameMarginLeft


class RGraphicsRectItem(QtGui.QGraphicsRectItem):
    def __init__(self, window, x0, y0, width, height):
        QtGui.QGraphicsRectItem.__init__(self, x0, y0, width, height, None, window.scene)
        self.window = window
        self.view = window.view


class RGraphicsPolygonItem(QtGui.QGraphicsPolygonItem):
    def __init__(self, window, polygon):
        QtGui.QGraphicsRectItem.__init__(self, polygon, None, window.scene)
        self.window = window
        self.view = window.view


class RFrame(QtGui.QFrame):
    """Customized class for color setting frames"""
    def __init__(self, color):
        QtGui.QFrame.__init__(self)
        
        self.setStyleSheet('QWidget { background-color: %s}' % color.name())
        self.color = color.name()
    
    def mousePressEvent(self, event):
        
        color = QtGui.QColorDialog().getColor()        
        if color.isValid():
            self.setStyleSheet('QWidget { background-color: %s}' % color.name())
            self.color = color.name()
